package ULHT.LIG2N1.RC.CLIENT;

import ULHT.LIG2N1.RC.CLIENTINTERFACE.Home;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketException;
import java.util.InputMismatchException;
import java.util.Scanner;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextArea;

public class TCPclientThread extends Thread {

    Socket socket;
    private boolean erro = false;
    private BufferedReader in;
    private PrintWriter out;
    private ClientTokenAndState token;

    public TCPclientThread(Socket ds, ClientTokenAndState token) throws SocketException {
        try {
            this.socket = ds;
            this.token = token;
            // dois segundos de timeout até considerar que a ligação caiu
            this.socket.setSoTimeout(4000);
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            out = new PrintWriter(this.socket.getOutputStream(), true);
        } catch (IOException ex) {
            System.out.println("Erro na leitura do socket!");
        }
    }

    @Override
    public void run() {
        String serverReply;
        // se ainda não houver token é porque é uma ligação nova
        if (token.getToken().isEmpty()) {
            try {
                // na ligação INICIAL o primeiro contacto é 
                // cliente  -> "HANDSHAKE"
                // servidor -> "HELLO" ou "FORBIDDEN"
                // servidor -> (caso HELLO)token
                out.println("HANDSHAKE");
                serverReply = readFromServerEvo();
                if (serverReply.equals("FORBIDDEN")) {
                    System.out.println("O seu endereço IP foi rejeitado pelo servidor.\n\nPrograma terminado\n");
                    return;
                }
                if (!serverReply.equals("HELLO")) {
                    // caso o servidor não responda correctamente ao handshake,
                    // termina a ligação
                    System.out.println("Servidor não respondeu ao HANDSHAKE\n*** ABORTADO ***\n");
                    in.close();
                    out.close();
                    return;
                } else {
                    token.setToken(readFromServerEvo());
                    System.out.println(token.getToken());
                }
            } catch (IOException ex) {
                Logger.getLogger(TCPclientThread.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            try {
                // se já há um token é porque a ligação caiu, pedir um Reconnect
                // no RECONNECT o primeiro contacto é 
                // cliente  -> "HANDSHAKE"
                // servidor -> "GRANTED" ou "FORBIDDEN", conforme o caso
                writeToServer("REJOIN");
                if (!readFromServerEvo().equals("GRANTED")) {
                    //TODO: quando falha o rejoin, temos pena, fechar tudo.
                }
            } catch (IOException ex) {
                Logger.getLogger(TCPclientThread.class.getName()).log(Level.SEVERE, null, ex);
            }

        }
        // faz a interacção entre o cliente e o servidor
        handleInteraction();
    }

    private void showMenu() {
        System.out.println("*********** Net Client ***********");
        System.out.println();
        System.out.println("MENU QUESTIONÁRIO");
        System.out.println();
        System.out.println("0  - Menu Principal");
        System.out.println("1  - Participantes Inscritos");
        System.out.println("2  - Participantes Online");
        System.out.println("3  - Participantes Desclassificados");
        System.out.println("4  - Hora do Servidor");
        System.out.println("5  - Hora do Inicio");
        System.out.println("6  - Responder");
        System.out.println("77 - Ajuda");
        System.out.println("88 - Testar Unicast UDP");
        System.out.println("99 - Sair");
        System.out.println();
    }

    private void handleInteraction() {
        Scanner userEntry;
        String opcaostring = "";
        int opcao = -1;
        String serverReply = "";

        //mostra o menu
        showMenu();
        do {

            // para controlo da validade da opção
            boolean validInput = false;

            //Solicita ao utilizador a opção pretendida
            do {
                if (!validInput) {
                    System.out.print("\nIntroduza a opção: ");
                    try {
                        userEntry = new Scanner(System.in);
                        opcao = userEntry.nextInt();
                    } catch (InputMismatchException ex) {
                        opcao = -1;
                    }
                }

                //Recolhe opção de menu introduzida pelo cliente
                switch (opcao) {
                    case 0:
                        validInput = true;
                        showMenu();
                        break;
                    case 1:
                        validInput = true;
                        opcaostring = "WHITELIST"; //System.out.println("Solicita ao Server a lista de participantes Inscritos");
                        break;
                    case 2:
                        validInput = true;
                        opcaostring = "PLAYERS"; //System.out.println("Solicita ao Server a lista de participantes Online");
                        break;
                    case 3:
                        validInput = true;
                        opcaostring = "BLACKLIST"; //System.out.println("Solicita ao Server a lista de participantes Desclassificados");
                        break;
                    case 4:
                        validInput = true;
                        opcaostring = "TIME"; //System.out.println("Solicita ao Server a lista de Hora do Servidor");
                        break;
                    case 5:
                        validInput = true;
                        opcaostring = "NEXTGAME"; //System.out.println("Solicita ao Server a lista de Hora Inicio Jogo");
                        break;
                    case 6:
                        validInput = true;
                        opcaostring = "PLAY"; //System.out.println("Solicita ao Server uma questão!");
                        break;
                    case 77:
                        validInput = true;
                        opcaostring = "HELP";
                        break;
                    case 88:
                        validInput = true;
                        opcaostring = "TESTUNICAST";
                        break;
                    case 99:
                        validInput = true;
                        opcaostring = "QUIT";
                        quitClient();
                        break;
                }
                if (!validInput) {
                    System.out.println("Opção inválida!");
                }
            } while (!validInput);

            // envia o pedido ao servidor
            writeToServer(opcaostring);

            // Lê a resposta do servidor
            // Casos especiais:
            // Se for um teste ao unicast, uma pergunta ou o quit, são
            // tratadas individualmente, caso contrário, faz o output da 
            // mensagem do servidor

            if (opcao == 6) {
                handleGame();
            } else if (opcao != 88 && opcao != 99) {
                try {
                    serverReply = readFromServerEvo();
                    if (serverReply.equalsIgnoreCase("ERR")) {
                        System.out.println(readFromServerEvo());
                    } else {
                        System.out.println(serverReply);
                        serverReply = null;
                    }
                } catch (IOException ex) {
                    Logger.getLogger(TCPclientThread.class.getName()).log(Level.SEVERE, null, ex);
                }
            } else if (opcao == 99) {
                // TODO tratar do quit
            }

        } while (opcao != 99);
    }

    private void handleGame() {
        try {
            ExecutorService executor = Executors.newSingleThreadExecutor();
            Future<String> input = executor.submit(new GetAnswer());
            boolean validInput = true;
            String resposta = null;
            long timeout = 0;
            Home home = null;

            try {
                home = new Home(socket);
            } catch (IOException ex) {
                Logger.getLogger(TCPclientThread.class.getName()).log(Level.SEVERE, null, ex);
            }

            do {
                try {
                    resposta = readFromServerEvo();
                    if (resposta.equalsIgnoreCase("SENDQUESTIONTIMEOUT")) {

                        writeToServer("READY");

                        timeout = Long.parseLong(readFromServerEvo());

                        writeToServer("GETQUESTION");

                        if (readFromServerEvo().equals("STARTANSWERSESSION")) {
                            writeToServer("READY");
                            try {
                                // recebe o input da resposta (em várias linhas por causa dos \n);
                                //Home home = new Home();

                                /*
                                 * String concat = ""; for (int i = 1; i <=7;
                                 * i++) concat += readFromServerEvo() + "\n";
                                 * System.out.print(concat);
                                 * interfaceCoisinho(concat);
                                 *
                                 */

                                /*
                                 * System.out.println(readFromServerEvo());
                                 * System.out.println(readFromServerEvo());
                                 * System.out.println(readFromServerEvo());
                                 * System.out.println(readFromServerEvo());
                                 * System.out.println(readFromServerEvo());
                                 * System.out.println(readFromServerEvo());
                                 * System.out.println(readFromServerEvo());
                                 */
                                /*
                                 * String teste = "";
                                 *
                                 * for (int i = 1; i <= 7; i++) { teste +=
                                 * readFromServerEvo() + "\n"; }
                                 */

                                home.SetPergunta(readFromServerEvo());
                            } catch (IOException ex) {
                                Logger.getLogger(TCPclientThread.class.getName()).log(Level.SEVERE, null, ex);
                            }

                            do {
                                if (!validInput) {
                                    System.out.println("Resposta inválida, por favor introduza 'A', 'B', 'C' ou 'D' seguido de ENTER");
                                }
                                System.out.print("\nQual a sua resposta? : ");
                                try {
                                    try {
                                        try {
                                            resposta = input.get(timeout, TimeUnit.MILLISECONDS).toUpperCase();
                                        } catch (InterruptedException | ExecutionException ex) {
                                            System.out.println("\n ** excepção **\n");
                                            ex.printStackTrace();
                                        }
                                    } catch (TimeoutException ex) {
                                        try (Scanner a = new Scanner(System.in)) {
                                            if (a.hasNext()) {
                                                a.next();
                                            } else {
                                                a.close();
                                            }
                                        }
                                        System.out.flush();
                                        resposta = "Y";
                                        System.out.println("\n ** TERMINOU O TEMPO **\n");
                                    }
                                    executor.shutdownNow();
                                    break;
                                } catch (InputMismatchException ex) {
                                    resposta = "X";
                                }
                                if (!resposta.equals("A") && !resposta.equals("B") && !resposta.equals("C") && !resposta.equals("D") && !resposta.equals("Y")) {
                                    validInput = false;
                                } else {
                                    validInput = true;
                                }
                            } while (!validInput);
                            // envia a resposta ao servidor
                            writeToServer(resposta);

                            // espera a aceitação da resposta?
                            // recebe o resultado (certo ou errado) e o tempo
                            System.out.println(readFromServerEvo());
                        } else {
                            System.out.println("Erro de comunicação");
                        }
                    }
                } catch (IOException ex) {
                    Logger.getLogger(TCPclientThread.class.getName()).log(Level.SEVERE, null, ex);
                }
            } while (!resposta.equalsIgnoreCase("GAMEEND") || !resposta.equalsIgnoreCase("ERR") || !resposta.equalsIgnoreCase("DISQUALIFIED"));
            switch (resposta.toUpperCase()) {
                case "DISQUALIFIED":
                    readFromServerEvo();
                    System.out.println(readFromServerEvo());
                    token.quit();
                    break;
                case "GAMEEND":
                    System.out.println(readFromServerEvo());
                    break;
            }
        } catch (IOException ex) {
            Logger.getLogger(TCPclientThread.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void quitClient() {
        System.out.println("A terminar a ligação");
        // como é um desligar explícito, usa-se o método quit
        token.quit();

        // TODO: ver que resposta se espera do servidor
    }

    private String readFromServerEvo() throws IOException {
        int msgSize;
        String serverReply;
        char[] buffer = null;
        do {
            serverReply = in.readLine();
            if (serverReply != null && serverReply.length() > 0) {
                if (serverReply.isEmpty()) {
                    throw new IOException("Sevidor não enviou o tamanho");
                } else {
                    try {
                        msgSize = Integer.parseInt(serverReply);
                    } catch (NumberFormatException nfe) {
                        throw new IOException("Tamanho no formato errado");
                    }
                    buffer = new char[msgSize];
                    int totalRead = 0;
                    while (totalRead < msgSize) {
                        int read = in.read(buffer, totalRead, msgSize - totalRead);
                        if (read == -1) {
                            throw new IOException("Dados insuficientes");
                        }
                        totalRead += read;
                    }
                }
            }
        } while (serverReply == null || serverReply.length() <= 0);

        return new String(buffer);
    }

    private String readFromServer() {
        String serverReply = null;
        char[] buffer = new char[65507];
        try {
            //in.read(buffer);
            //serverReply = new String(buffer);
            serverReply = in.readLine();
        } catch (IOException ex) {
            erro = true;
            System.out.println("Erro na leitura do socket!");
        }
        if (serverReply == null) {
            erro = true;
            System.out.println("Timeout na resposta do servidor");
        }
        return serverReply;
    }

    private void writeToServer(String output) {
        if (!socket.isClosed()) {
            out.println(output);
        }
    }

    public void interfaceCoisinho(String texto) {
        JFrame frame = new JFrame("Redes de Computadores");
        final JLabel label = new JLabel("interface");
        JTextArea textoPergunta = new JTextArea();
        textoPergunta.setText(texto);
        frame.getContentPane().add(label);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }
}
